#!/usr/bin/python3
###############################################################################
'''文本用户界面'''
# Copyright (c) 2022 Xu Ruijun | 1687701765@qq.com
#
# This file is part of Electronic Analog Filter Design Tool(eAFDTool).
#
# eAFDTool is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or any later version.
#
# eAFDTool is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# eAFDTool. If not, see <https://www.gnu.org/licenses/>.
###############################################################################
import curses
import math

from . import curr_str
from .expr2str import num_SIprefix
from .parser_str import parser_SInum_unit
from .tui import lblocks

__author__ = "Xu Ruijun"
__copyright__ = "Copyright (c) 2022 Xu Ruijun"
__license__ = "GPLv3 or later"


class FilterType:
    def __init__(self, win, parent):
        self.win = win
        self.parent = parent
        self.swin = []
        self.curr = 0

    def init_win(self):
        self.swin = []
        for i in range(len(curr_str.xxf)):
            win = self.win.derwin(3, 6, 0, i*7)
            win.addstr(0, 0, curr_str.xxf[i])
            win.refresh()
            self.swin.append(win)
        self.change_curr(0)

    def change_curr(self, new_curr):
        assert 0 <= new_curr < len(self.swin)
        self.swin[self.curr].addstr(0, 0, curr_str.xxf[self.curr])
        self.swin[self.curr].refresh()
        if self.curr//2+1 != new_curr//2+1:
            self.parent.freqi.change_mode(new_curr//2+1)
        self.curr = new_curr
        self.swin[self.curr].addstr(0, 0, curr_str.xxf[self.curr], curses.A_REVERSE)
        self.swin[self.curr].refresh()

    def show_cursor(self):
        self.swin[self.curr].move(2, 4)
        self.swin[self.curr].refresh()

    def rxkey(self, keycode):
        if keycode == curses.KEY_LEFT and self.curr > 0:
            self.change_curr(self.curr - 1)
        if keycode == curses.KEY_RIGHT and self.curr < len(self.swin) - 1:
            self.change_curr(self.curr + 1)
        if keycode == ord('\t'):
            self.show_cursor()


class InputBox:
    def __init__(self, win, s='', right=False):
        self.win = win
        self.s = s
        self.right = right

    def update(self, s):
        self.s = s
        wide = self.win.getmaxyx()[1]
        x = wide - len(s) - 1 
        if self.right:
            if x < 0:
                s = s[-x:]
                x = 0
            s2 = ' '*x + s
        else:
            if x < 0:
                s = s[:x]
                x = 0
            s2 = s + ' '*x
        self.win.addstr(0, 0, s2)
        self.show_cursor()

    def show_cursor(self):
        wide = self.win.getmaxyx()[1]
        x = wide - 2
        if x < 0:
            x = 0
        self.win.move(0, x) 
        self.win.refresh()

    def start_input(self, last):
        self.win.addch(0, 0, last)
        self.win.move(0, 1)
        self.win.refresh()
        curses.echo()
        curses.nocbreak()
        s = self.win.getstr()
        s = chr(last) + s.decode()
        curses.noecho()
        curses.cbreak()
        return s

    def rxkey(self, keycode):
        if keycode == ord('\t'):
            self.show_cursor()
        elif not chr(keycode).isspace():
            self.start_input(keycode)


class IntInputBox(InputBox):
    def __init__(self, win, i=0, right=True):
        super().__init__(win, str(i), right)

    def update(self, i):
        s = str(i)
        super().update(s)

    def start_input(self, last):
        s = super().start_input(last)
        i = int(s)
        self.update(i)
        return i

    def rxkey(self, keycode):
        if keycode == ord('\t'):
            self.show_cursor()
        elif ord('0') <= keycode <= ord('9'):
            self.start_input(keycode)


class FreqInputBox(InputBox):
    def __init__(self, win, freq, right=True):
        super().__init__(win, '', right)
        self.update(freq)
        self.rads = freq*2*math.pi

    def update(self, freq):
        s = num_SIprefix(freq)
        super().update(s + 'Hz')

    def input_num(self, last):
        s = self.start_input(last)
        num, unit = parser_SInum_unit(s)
        if unit.lower() in {'r', 'rad/s'}:
            num /= 2*math.pi
        self.update(num)
        self.rads = num*2*math.pi
        return self.rads

    def rxkey(self, keycode):
        if keycode == ord('\t'):
            self.show_cursor()
        elif ord('0') <= keycode <= ord('9') or keycode == ord('.'):
            self.input_num(keycode)


class FreqInput:
    def __init__(self, win):
        self.win = win
        self.mode = 1
        self.curr = 0
        self.swin = []

    def init_win(self):
        self.change_mode(1)

    def change_mode(self, mode):
        assert 1 <= mode <= 2
        wide = self.win.getmaxyx()[1]
        if self.swin:
            tmp = map(lambda x:math.log(x.rads), self.swin)
            tmp = sum(tmp)/len(self.swin)
            old_freq = math.exp(tmp)/(2*math.pi)
        else:
            old_freq = 1e3
        self.curr = 0
        self.swin = []
        self.mode = mode
        for i in range(mode):
            win = self.win.derwin(1, wide//mode-1, 0, wide//mode*i)
            if mode == 1:
                freq = old_freq
            elif i == 0:
                freq = old_freq * 0.5
            elif i == 1:
                freq = old_freq * 2
            fbox = FreqInputBox(win, freq)
            self.swin.append(fbox)

    def rxkey(self, keycode):
        if keycode == ord('\t'):
            self.swin[self.curr].show_cursor()
        elif keycode == curses.KEY_LEFT:
            if self.curr > 0:
                self.curr -= 1
                self.swin[self.curr].show_cursor()
        elif keycode == curses.KEY_RIGHT:
            if self.curr < len(self.swin) - 1:
                self.curr += 1
                self.swin[self.curr].show_cursor()
        else:
            self.swin[self.curr].rxkey(keycode)


class FilterFit:
    def __init__(self, win):
        self.win = win
        self.value = 0

    def init_win(self):
        wide = self.win.getmaxyx()[1] - 1
        self.win.addstr(1, 0, 'Bessel')
        self.win.addstr(1, wide//2-3, 'Butter')
        self.win.addstr(1, wide-6, 'Cheby1')
        self.update(0)

    def update(self, value):
        wide = self.win.getmaxyx()[1] - 1
        half = wide//2
        self.value = min(max(value, -1), 1)
        n = self.value*half*8
        if n >= 1:
            s = lblocks(round(n), half*8)
            self.win.addstr(0, 0, ' '*half)
            self.win.addstr(s)
        elif n <= -1:
            s = lblocks(round(n+half*8), half*8)
            self.win.addstr(0, 0, s, curses.A_REVERSE)
            self.win.addstr(' '*half)
        else:
            self.win.addstr(0, 0, ' '*(half-1)+'::'+' '*(half-1))
        self.show_cursor()

    def show_cursor(self):
        wide = self.win.getmaxyx()[1] - 1
        half = wide//2
        a = int(self.value*half*8)
        if a <= -1:
            self.win.move(1, 0)
        elif a >= 1:
            self.win.move(1, half*2)
        else:
            self.win.move(1, half)
        self.win.refresh()

    def rxkey(self, keycode):
        wide = self.win.getmaxyx()[1] - 1
        if keycode == curses.KEY_LEFT:
            self.update(self.value - 0.5/wide)
        elif keycode == curses.KEY_RIGHT:
            self.update(self.value + 0.5/wide)
        elif keycode == ord('\t'):
            self.show_cursor()


class TopologySelection:
    s_sk = 'Sallen-Key'
    s_mfb = 'Multi Feedback'
    def __init__(self, win, curr=0):
        self.win = win
        self.curr = curr

    def init_win(self):
        self.change(self.curr)

    def change(self, new_curr):
        wide = self.win.getmaxyx()[1] - 1
        self.curr = new_curr
        if self.curr == 0:
            self.win.addstr(0, 0, self.s_sk, curses.A_REVERSE)
            self.win.addstr(0, wide-len(self.s_mfb), self.s_mfb)
        else:
            self.win.addstr(0, 0, self.s_sk)
            self.win.addstr(0, wide-len(self.s_mfb), self.s_mfb, curses.A_REVERSE)
        self.show_cursor()

    def show_cursor(self):
        if self.curr == 0:
            self.win.move(0, 0)
        else:
            wide = self.win.getmaxyx()[1] - 2
            self.win.move(0, wide)
        self.win.refresh()

    def rxkey(self, keycode):
        if keycode == ord('\t'):
            self.show_cursor()
        elif keycode == curses.KEY_LEFT:
            self.change(0)
        elif keycode == curses.KEY_RIGHT:
            self.change(1)

class BoxOK:
    def __init__(self, win):
        self.win = win
        self.curr = 0

    def init_win(self):
        self.win.addstr(0, 0, "OK")
        self.win.addstr(0, 5, "Quit")
        self.win.refresh()

    def change(self, new_curr=None):
        if new_curr is not None:
            self.curr = new_curr
        if self.curr == 0:
            self.win.move(0, 0)
        else:
            self.win.move(0, 5)
        self.win.refresh()

    def rxkey(self, keycode):
        if keycode == ord('\t'):
            self.change()
        elif keycode == curses.KEY_LEFT:
            self.change(0)
        elif keycode == curses.KEY_RIGHT:
            self.change(1)
        elif keycode == ord('\n'):
            return self.curr


class InitSelection:
    def __init__(self, win):
        self.win = win
        self.curr = 0
        self.swin = []

    def init_win(self):
        wft = self.win.derwin(3, 28, 0, 0)
        self.ftype = FilterType(wft, self)
        self.ftype.init_win()

        wfi = self.win.derwin(1, 28, 4, 0)
        self.freqi = FreqInput(wfi)
        self.freqi.init_win()

        wff = self.win.derwin(2, 28, 6, 0)
        self.ffit = FilterFit(wff)
        self.ffit.init_win()

        wno = self.win.derwin(1, 28, 9, 0)
        self.nord = IntInputBox(wno, 3, right=True)
        self.nord.update(3)

        wto = self.win.derwin(1, 28, 11, 0)
        self.topo = TopologySelection(wto)
        self.topo.init_win()

        wok = self.win.derwin(1, 18, 13, 10)
        self.bok = BoxOK(wok)
        self.bok.init_win()
        self.swin = [self.ftype, self.freqi, self.ffit, self.nord, self.topo, self.bok]

    def rxkey(self, keycode):
        if not self.swin:
            return
        elif keycode == curses.KEY_UP:
            self.curr = max(self.curr-1, 0)
            keycode = ord('\t')
        elif keycode == curses.KEY_DOWN:
            self.curr = min(self.curr+1, len(self.swin)-1)
            keycode = ord('\t')
        r = self.swin[self.curr].rxkey(keycode)
        if self.curr == len(self.swin) - 1:
            return r

    def run(self, stdscr):
        while True:
            keycode = stdscr.getch()
            if keycode == ord('q'):
                break
            r = self.rxkey(keycode)
            if r == 0:
                ftype = curr_str.nxf[self.ftype.curr]
                frads = tuple(map(lambda x:x.rads, self.freqi.swin))
                ffit = self.ffit.value
                nord = int(self.nord.s)  # TODO: change to save int, not str
                topo = ['sk', 'mfb'][self.topo.curr]
                return ftype, frads, ffit, nord, topo
            elif r is not None:
                return r
